package main

import (
	"contract_deploy/contracts/portal"
	"contract_deploy/contracts/trade"
	"contract_deploy/contracts/trust"
	"contract_deploy/global"
	"encoding/hex"
	"encoding/json"
	"log"
	"os"
	"os/signal"
	"path"
	"runtime"
	"syscall"

	"github.com/FISCO-BCOS/go-sdk/client"
	"github.com/FISCO-BCOS/go-sdk/conf"
	"github.com/FISCO-BCOS/go-sdk/smcrypto"
	"github.com/cockroachdb/errors"
	"github.com/ethereum/go-ethereum/common"
	"go.uber.org/zap"
)

type ContractInfo struct {
	Address string `json:"address"`
	TxHash  string `json:"tx_hash"`
}

func main() {

	quit := make(chan os.Signal, 1)
	defer func() {
		if err := recover(); err != nil {
			zap.L().Error("运行时错误", zap.Any("err", err))
			quit <- syscall.SIGQUIT
		}
	}()

	// 获取是否是debug模式
	isRelease := os.Getenv("HIGHWAY_RELEASE") == "true"

	// 初始化logger
	global.InitLogger(!isRelease)

	// 初始化配置
	global.InitConfig()

	// 初始化 cli
	cli, deployerAddress, err := initClientAndDeployerAddress()
	if err != nil {
		global.Logger.Error(err.Error())
		return
	}

	// 初始化 account address
	deployer, portalProver, tradeProver, tradeAuditor, err := initAddresses()
	if err != nil {
		global.Logger.Error(err.Error())
		return
	}
	if deployer.Hex() != deployerAddress.Hex() {
		global.Logger.Error("deployer config are not the same(config.toml,config.yaml)")
		return
	}
	global.Logger.Debug(deployer.Hex())
	global.Logger.Debug(deployerAddress.Hex())
	global.Logger.Debug(portalProver.Hex())
	global.Logger.Debug(tradeProver.Hex())
	global.Logger.Debug(tradeAuditor.Hex())

	// 1. deploy portal manager
	portalManager, _, err := deployPortalManagerContract(cli, deployerAddress)
	if err != nil {
		global.Logger.Error(err.Error())
		return
	}

	// 2. grant portal prover
	err = grantPortalProver(portalManager, cli, portalProver)
	if err != nil {
		global.Logger.Error(err.Error())
		return
	}

	// 3. deploy trust manager
	trustManager, trustAddress, err := deployTrustManagerContract(cli, deployerAddress)
	if err != nil {
		global.Logger.Error(err.Error())
		return
	}

	// 4. deploy trade manager
	tradeManager, tradeAddress, err := deployTradeManagerContract(cli, deployerAddress, trustAddress)
	if err != nil {
		global.Logger.Error(err.Error())
		return
	}

	// 5. grant trade prover
	err = grantTradeProver(tradeManager, cli, tradeProver)
	if err != nil {
		global.Logger.Error(err.Error())
		return
	}
	// 6. grant trade auditor
	err = grantTradeAuditor(tradeManager, cli, tradeAuditor)
	if err != nil {
		global.Logger.Error(err.Error())
		return
	}

	// 7. grant trade scorer to trade manager for trust manager
	err = grantTrustTradeScorer(trustManager, cli, tradeAddress)
	if err != nil {
		global.Logger.Error(err.Error())
		return
	}

	// 优雅退出
	// kill -9 是捕捉不到的
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	// 同步log信息
	zap.S().Sync()

}

// initClientAndDeployerAddress 初始化client以及对应的deployer的地址
func initClientAndDeployerAddress() (cli *client.Client, deployer common.Address, err error) {
	// 加载client配置文件信息
	configs, err := conf.ParseConfigFile("config.toml")
	if err != nil {
		return nil, common.Address{}, errors.Wrap(err, "parse config.toml failed")
	}
	config := &configs[0]

	// 国密获取帐号地址的方式
	deployerAddress := smcrypto.SM2KeyToAddress(config.PrivateKey)
	global.Logger.Debugf("deployer is %s\n", deployerAddress.Hex())

	cli, err = client.Dial(config)
	if err != nil {
		log.Fatal(err)
	}

	global.Logger.Debugf("deployer private key is %s\n", hex.EncodeToString(config.PrivateKey))
	return cli, deployerAddress, nil
}

// initAddresses 初始化地址
func initAddresses() (deployer common.Address, portalProver common.Address, tradeProver common.Address, tradeAuditor common.Address, err error) {
	// deployer
	deployerKey, _, err := conf.LoadECPrivateKeyFromPEM("./account/" + global.ServiceConfig.Deployer + ".pem")
	if err != nil {
		return common.Address{}, common.Address{}, common.Address{}, common.Address{}, errors.Wrap(err, "load deployer failed")
	}
	deployer = smcrypto.SM2KeyToAddress(deployerKey)

	// portal prover
	portalProverKey, _, err := conf.LoadECPrivateKeyFromPEM("./account/" + global.ServiceConfig.PortalProver + ".pem")
	if err != nil {
		return common.Address{}, common.Address{}, common.Address{}, common.Address{}, errors.Wrap(err, "load portal prover failed")
	}
	portalProver = smcrypto.SM2KeyToAddress(portalProverKey)

	// trade prover
	tradeProverKey, _, err := conf.LoadECPrivateKeyFromPEM("./account/" + global.ServiceConfig.TradeProver + ".pem")
	if err != nil {
		return common.Address{}, common.Address{}, common.Address{}, common.Address{}, errors.Wrap(err, "load trade prover failed")
	}
	tradeProver = smcrypto.SM2KeyToAddress(tradeProverKey)

	// trade auditor
	tradeAuditorKey, _, err := conf.LoadECPrivateKeyFromPEM("./account/" + global.ServiceConfig.TradeAuditor + ".pem")
	if err != nil {
		return common.Address{}, common.Address{}, common.Address{}, common.Address{}, errors.Wrap(err, "load trade auditor failed")
	}
	tradeAuditor = smcrypto.SM2KeyToAddress(tradeAuditorKey)

	return deployer, portalProver, tradeProver, tradeAuditor, nil
}

/*======================portal manager=======================*/

// deployPortalManagerContract 部属 portal manager
func deployPortalManagerContract(cli *client.Client, defaultAdmin common.Address) (portalManager *portal.Portal, portalAddress common.Address, err error) {

	_, filename, _, _ := runtime.Caller(0)
	root := path.Dir(filename)
	targetPath := path.Join(root, "address", "portal.json")

	data, err := os.ReadFile(targetPath)
	if err != nil {
		if os.IsNotExist(err) {
			// 部属合约
			address, tx, instance, err := portal.DeployPortal(cli.GetTransactOpts(), cli, defaultAdmin) // deploy portal contract
			if err != nil {
				log.Fatal(err)
			}
			// save this to local file or db file
			log.Println("contract address: ", address.Hex()) // the address should be saved
			log.Println("transaction hash: ", tx.Hash().Hex())
			contractInfo := ContractInfo{
				Address: address.Hex(),
				TxHash:  tx.Hash().Hex(),
			}
			data, err := json.MarshalIndent(contractInfo, "", "\t")
			if err != nil {
				return nil, common.Address{}, errors.Wrap(err, "json marshal with indent failed")
			}
			// 记录地址和交易哈希
			err = os.WriteFile(targetPath, data, 0755)
			if err != nil {
				return nil, common.Address{}, errors.Wrap(err, "wirte file failed")
			}
			return instance, address, nil
		}
		return nil, common.Address{}, errors.Wrap(err, "read file failed")
	}

	// 已经部署过了
	contractInfo := ContractInfo{}
	err = json.Unmarshal(data, &contractInfo)
	if err != nil {
		return nil, common.Address{}, errors.Wrap(err, "json unmarshal failed")
	}
	log.Println("contract address: ", contractInfo.Address)
	log.Println("transaction hash: ", contractInfo.TxHash)

	// load contract
	contractAddress := common.HexToAddress(contractInfo.Address)
	portalInstance, err := portal.NewPortal(contractAddress, cli)
	if err != nil {
		log.Fatal(err)
	}
	return portalInstance, contractAddress, nil
}

// grantPortalProver 设置 portal prover 权限
func grantPortalProver(portalManager *portal.Portal, cli *client.Client, portalProver common.Address) error {

	portalProverRole, err := portalManager.PORTALPROVERROLE(cli.GetCallOpts())
	if err != nil {
		return errors.Wrap(err, "get portal prover role bytes failed")
	}
	isPortalProverRole, err := portalManager.HasRole(cli.GetCallOpts(), portalProverRole, portalProver)
	if err != nil {
		return errors.Wrap(err, "get is portal prover has role failed")
	}
	if !isPortalProverRole {
		global.Logger.Debug("portal prover is not portal prover role granted")
		tx, receipt, err := portalManager.GrantRole(cli.GetTransactOpts(), portalProverRole, portalProver)
		if err != nil {
			return errors.Wrap(err, "grant portal prover role failed")
		}
		txHash1 := tx.Hash()
		txHash1.Hex()
		txHash2 := receipt.TransactionHash
		global.Logger.Debugf("grant portal prover txHash1: %s\n", txHash1)
		global.Logger.Debugf("grant portal prover txHash2: %s\n", txHash2)
	} else {
		global.Logger.Debug("portal prover already has portal prover role granted")
	}
	return nil
}

/*======================trust manager=======================*/

// deployTrustManagerContract 部属 trust manager
func deployTrustManagerContract(client *client.Client, defaultAdmin common.Address) (trustManager *trust.Trust, trustAddress common.Address, err error) {

	_, filename, _, _ := runtime.Caller(0)
	root := path.Dir(filename)
	targetPath := path.Join(root, "address", "trust.json")

	data, err := os.ReadFile(targetPath)
	if err != nil {
		if os.IsNotExist(err) {
			// 部属合约
			address, tx, instance, err := trust.DeployTrust(client.GetTransactOpts(), client, defaultAdmin) // deploy portal contract
			if err != nil {
				log.Fatal(err)
			}
			// save this to local file or db file
			log.Println("contract address: ", address.Hex()) // the address should be saved
			log.Println("transaction hash: ", tx.Hash().Hex())
			contractInfo := ContractInfo{
				Address: address.Hex(),
				TxHash:  tx.Hash().Hex(),
			}
			data, err := json.MarshalIndent(contractInfo, "", "\t")
			if err != nil {
				return nil, common.Address{}, errors.Wrap(err, "json marshal with indent failed")
			}
			// 记录地址和交易哈希
			err = os.WriteFile(targetPath, data, 0755)
			if err != nil {
				return nil, common.Address{}, errors.Wrap(err, "wirte file failed")
			}
			return instance, address, nil
		}
		return nil, common.Address{}, errors.Wrap(err, "read file failed")
	}

	// 已经部署过了
	contractInfo := ContractInfo{}
	err = json.Unmarshal(data, &contractInfo)
	if err != nil {
		return nil, common.Address{}, errors.Wrap(err, "json unmarshal failed")
	}
	log.Println("contract address: ", contractInfo.Address)
	log.Println("transaction hash: ", contractInfo.TxHash)

	// load contract
	contractAddress := common.HexToAddress(contractInfo.Address)
	trustInstance, err := trust.NewTrust(contractAddress, client)
	if err != nil {
		log.Fatal(err)
	}
	return trustInstance, contractAddress, nil
}

// grantTrustTradeScorer 设置 trade score 权限
func grantTrustTradeScorer(trustManager *trust.Trust, cli *client.Client, tradeManager common.Address) error {

	tradeScoreRole, err := trustManager.TRADESCOREROLE(cli.GetCallOpts())
	if err != nil {
		return errors.Wrap(err, "get trade score role bytes failed")
	}
	isTradeProverRole, err := trustManager.HasRole(cli.GetCallOpts(), tradeScoreRole, tradeManager)
	if err != nil {
		return errors.Wrap(err, "get is trade manager has role failed")
	}
	if !isTradeProverRole {
		global.Logger.Debug("trade manager is not trade score role granted")
		tx, receipt, err := trustManager.GrantRole(cli.GetTransactOpts(), tradeScoreRole, tradeManager)
		if err != nil {
			return errors.Wrap(err, "grant trade score role failed")
		}
		txHash1 := tx.Hash()
		txHash1.Hex()
		txHash2 := receipt.TransactionHash
		global.Logger.Debugf("grant trade score txHash1: %s\n", txHash1)
		global.Logger.Debugf("grant trade score txHash2: %s\n", txHash2)
	} else {
		global.Logger.Debug("trade manager already has trade score role granted")
	}
	return nil
}

/*======================trade manager=======================*/

// deployTradeManagerContract 部属 trade manager
func deployTradeManagerContract(cli *client.Client, defaultAdmin common.Address, trustAddress common.Address) (tradeManager *trade.Trade, tradeAddress common.Address, err error) {

	_, filename, _, _ := runtime.Caller(0)
	root := path.Dir(filename)
	targetPath := path.Join(root, "address", "trade.json")

	data, err := os.ReadFile(targetPath)
	if err != nil {
		if os.IsNotExist(err) {
			// 部属合约
			address, tx, instance, err := trade.DeployTrade(cli.GetTransactOpts(), cli, defaultAdmin, tradeAddress) // deploy portal contract
			if err != nil {
				log.Fatal(err)
			}
			// save this to local file or db file
			log.Println("contract address: ", address.Hex()) // the address should be saved
			log.Println("transaction hash: ", tx.Hash().Hex())
			contractInfo := ContractInfo{
				Address: address.Hex(),
				TxHash:  tx.Hash().Hex(),
			}
			data, err := json.MarshalIndent(contractInfo, "", "\t")
			if err != nil {
				return nil, common.Address{}, errors.Wrap(err, "json marshal with indent failed")
			}
			// 记录地址和交易哈希
			err = os.WriteFile(targetPath, data, 0755)
			if err != nil {
				return nil, common.Address{}, errors.Wrap(err, "wirte file failed")
			}
			return instance, address, nil
		}
		return nil, common.Address{}, errors.Wrap(err, "read file failed")
	}

	// 已经部署过了
	contractInfo := ContractInfo{}
	err = json.Unmarshal(data, &contractInfo)
	if err != nil {
		return nil, common.Address{}, errors.Wrap(err, "json unmarshal failed")
	}
	log.Println("contract address: ", contractInfo.Address)
	log.Println("transaction hash: ", contractInfo.TxHash)

	// load contract
	contractAddress := common.HexToAddress(contractInfo.Address)
	tradeInstance, err := trade.NewTrade(contractAddress, cli)
	if err != nil {
		log.Fatal(err)
	}
	return tradeInstance, contractAddress, nil
}

// grantTradeProver 设置 trade prover 权限
func grantTradeProver(tradeManager *trade.Trade, cli *client.Client, tradeProver common.Address) error {

	tradeProverRole, err := tradeManager.TRADEPROVERROLE(cli.GetCallOpts())
	if err != nil {
		return errors.Wrap(err, "get trade prover role bytes failed")
	}
	isTradeProverRole, err := tradeManager.HasRole(cli.GetCallOpts(), tradeProverRole, tradeProver)
	if err != nil {
		return errors.Wrap(err, "get is trade prover has role failed")
	}
	if !isTradeProverRole {
		global.Logger.Debug("trade prover is not trade prover role granted")
		tx, receipt, err := tradeManager.GrantRole(cli.GetTransactOpts(), tradeProverRole, tradeProver)
		if err != nil {
			return errors.Wrap(err, "grant trade prover role failed")
		}
		txHash1 := tx.Hash()
		txHash1.Hex()
		txHash2 := receipt.TransactionHash
		global.Logger.Debugf("grant trade prover txHash1: %s\n", txHash1)
		global.Logger.Debugf("grant trade prover txHash2: %s\n", txHash2)
	} else {
		global.Logger.Debug("trade prover already has trade prover role granted")
	}
	return nil
}

// grantTradeAuditor 设置 trade auditor 权限
func grantTradeAuditor(tradeManager *trade.Trade, cli *client.Client, tradeAuditor common.Address) error {

	tradeAuditorRole, err := tradeManager.TRADEAUDITROLE(cli.GetCallOpts())
	if err != nil {
		return errors.Wrap(err, "get trade auditor role bytes failed")
	}
	isTradeAuditorRole, err := tradeManager.HasRole(cli.GetCallOpts(), tradeAuditorRole, tradeAuditor)
	if err != nil {
		return errors.Wrap(err, "get is trade auditor has role failed")
	}
	if !isTradeAuditorRole {
		global.Logger.Debug("trade auditor is not trade auditor role granted")
		tx, receipt, err := tradeManager.GrantRole(cli.GetTransactOpts(), tradeAuditorRole, tradeAuditor)
		if err != nil {
			return errors.Wrap(err, "grant trade auditor role failed")
		}
		txHash1 := tx.Hash()
		txHash1.Hex()
		txHash2 := receipt.TransactionHash
		global.Logger.Debugf("grant trade auditor txHash1: %s\n", txHash1)
		global.Logger.Debugf("grant trade auditor txHash2: %s\n", txHash2)
	} else {
		global.Logger.Debug("trade auditor already has trade auditor role granted")
	}
	return nil
}
